﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WaveTagLibrary
{
    public class WaveFile
    {
		public readonly string Filename;

		public WaveFile(string filename)
		{
			if (filename == null)
				throw new ArgumentNullException("filename");

			Filename = filename;
			Tags = new List<TagInfo>();
		}

		private uint[] riffPart = new uint[3];
		private long fmtOffset;

		public List<TagInfo> Tags { get; private set; }

		public bool SetTagValue(string tagName, string newValue)
		{
			if (Tags == null)
				return false;

			var foundTags = Tags.Where(t => t.TagName == tagName).ToArray();

			if (foundTags.Length == 0 || foundTags.Length > 1)
			{
				foreach (var tag in foundTags)
					Tags.Remove(tag);
				Tags.Add(new TagInfo { TagName = tagName, Data = Encoding.ASCII.GetBytes(newValue), });
			}
			else // if (foundTags.Length == 1)
			{
				foundTags[0].Data = Encoding.ASCII.GetBytes(newValue);
			}

			return true;
		}

		public async Task<bool> LoadAsync()
		{
			byte[] listBuffer;
			Tags.Clear();

			using (var reader = new BinaryReader(new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read)))
			{
				// read RIFF part
				for (int i = 0; i < riffPart.Length; i++)
					riffPart[i] = reader.ReadUInt32();

				// read LIST part
				if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "LIST")
					return false;

				var listSize = reader.ReadUInt32();

				if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "INFO")
					return false;
				listSize -= 4;

				listBuffer = new byte[listSize];

				await reader.BaseStream.ReadAsync(listBuffer, 0, listBuffer.Length);

				// keep track of all remaining data position
				fmtOffset = reader.BaseStream.Position;
			}

			using (var reader = new BinaryReader(new MemoryStream(listBuffer)))
			{
				while (reader.BaseStream.Position < reader.BaseStream.Length)
				{
					Tags.Add(await TagInfo.ReadAsync(reader));
				}
			}

			return true;
		}

		public async Task SaveAsync()
		{
			var ms = new MemoryStream();
			using (var writer = new BinaryWriter(ms))
			{
				for (int i = 0; i < riffPart.Length; i++)
					writer.Write(riffPart[i]);

				if (Tags.Count > 0)
				{
					writer.Write(Encoding.ASCII.GetBytes("LIST"), 0, 4);

					uint listSize = 4 + ComputeTagsSize();
					writer.Write(listSize);

					writer.Write(Encoding.ASCII.GetBytes("INFO"), 0, 4);

					foreach (var tag in Tags)
						await tag.WriteAsync(writer);
				}

				using (var reader = new BinaryReader(new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read)))
				{
					reader.BaseStream.Position = fmtOffset;
					await reader.BaseStream.CopyToAsync(writer.BaseStream);
				}

				uint chunkSize = (uint)writer.BaseStream.Position - 8;
				writer.BaseStream.Position = 4;
				writer.Write(chunkSize);
			}

			File.WriteAllBytes(Filename, ms.ToArray());
		}

		private uint ComputeTagsSize()
		{
			uint size = 0;

			foreach (var tag in Tags)
				size += 8 + tag.GetOnFileDataSize(); // 8 = 4 bytes for tag name + 4 bytes for tag data size

			return size;
		}
    }
}
